
package dovs.peephole;

import dovs.peephole.node.*;
import dovs.peephole.analysis.*;
import dovs.instructions.*;

import java.util.*;
import java.lang.reflect.*;

public aspect Prepare extends DepthFirstAdapter {
	public int PPatternDecl.click_count = 0;

	public int TIntegerLiteral.value;
	public Integer AIntConstExp.value;
	public Condition ACondConstExp.value;

	public Set<PVar> ADisjunctionExp.bound_vars;

	public Class<?/* extends Instruction*/> POpcode.inst_class;
	public int POpcode.inst_id;
	public Class<?>[] POpcode.params;

	private Class<?> PExp.type;

	private Map<PVar, Class<?>> var_types;

	private void defineVar(PVar var, Class<?> type) {
		if (var_types.containsKey(var)) {
			throw new PeepholeException("Redefinition of variable "+var.getIdentifier().getText());
		}
		var_types.put(var, type);
	}

	private Class<?> lookupVar(PVar var) {
		if (!var_types.containsKey(var)) {
			throw new PeepholeException("Undefined variable "+var.getIdentifier().getText());
		}
		return var_types.get(var);
	}

	private static String typeName(Class<?> type) {
		String name = type.getName();
		return name.substring(name.lastIndexOf('.')+1);
	}

	private static void typeCheck(PExp exp, Class<?> type) {
		if (exp.type != type) {
			throw new PeepholeException("Type error: "+typeName(exp.type)+" given, "+typeName(type)+" expected");
		}
	}

	public @Override void inAPatternDecl(APatternDecl pattern) {
		PeepholeDriver.current_pattern = pattern;
		var_types = new HashMap<PVar, Class<?>>();
		var_types.put(pattern.getInstvar(), InstructionList.class);
	}

	public @Override void caseTIntegerLiteral(TIntegerLiteral lit) {
		lit.value = Integer.parseInt(lit.getText());
	}

	public @Override void outAIntConstExp(AIntConstExp exp) {
		int value = exp.getIntegerLiteral().value;
		if (exp.getSign() != null) {
			value = -value;
		}
		exp.value = value;
		exp.type = Integer.TYPE;
	}

	public @Override void outACondConstExp(ACondConstExp exp) {
		exp.value = Condition.valueOf(exp.getCond().getToken().getText().toUpperCase());
		exp.type = Condition.class;
	}

	@SuppressWarnings("unchecked")
	public @Override void outAOpcode(AOpcode opcode) {
		if (opcode.getIdentifier() == null) {
			opcode.setIdentifier(new TIdentifier("label"));
		}
		String cname = "dovs.instructions.I"+opcode.getIdentifier().getText();
		try {
			opcode.inst_class = Class.forName(cname);
			opcode.inst_id = Instruction.getID((Class<? extends Instruction>)opcode.inst_class);
			Constructor con = opcode.inst_class.getConstructors()[0];
			opcode.params = con.getParameterTypes();
		} catch (ClassNotFoundException e) {
			throw new PeepholeException("Unknown instruction "+opcode.getIdentifier().getText());
		}
	}

	public @Override void outAInst(AInst inst) {
		if (inst.getArgs().size() != inst.getOpcode().params.length) {
			throw new PeepholeException("Wrong number of arguments for opcode "+inst.getOpcode().getIdentifier().getText());
		}
		int a = 0;
		for (PExp arg : inst.getArgs()) {
			if (arg.type != inst.getOpcode().params[a++]) {
				throw new PeepholeException("Wrong argument types for opcode "+inst.getOpcode().getIdentifier().getText());
			}
		}
	}

	public @Override void outAInstructionInstPat(AInstructionInstPat instp) {
		if (instp.getParams().size() != instp.getOpcode().params.length) {
			throw new PeepholeException("Wrong number of parameters for opcode "+instp.getOpcode().getIdentifier().getText());
		}
		int a = 0;
		for (AVar param : instp.getParams()) {
			defineVar(param, instp.getOpcode().params[a++]);
		}
	}

	public @Override void outALabelBinderInstPat(ALabelBinderInstPat instp) {
		defineVar(instp.getLabel(), Label.class);
	}

	public @Override void outAVarExp(AVarExp exp) {
		exp.type = lookupVar(exp.getVar());
	}

	public @Override void outAMatchExp(AMatchExp exp) {
		typeCheck(exp.getLeft(), InstructionList.class);
		exp.type = Boolean.TYPE;
	}

	public @Override void outAConjunctionExp(AConjunctionExp exp) {
		typeCheck(exp.getLeft(), Boolean.TYPE);
		typeCheck(exp.getRight(), Boolean.TYPE);
		exp.type = Boolean.TYPE;
	}

	public @Override void caseADisjunctionExp(ADisjunctionExp exp) {
		Map<PVar,Class<?>> before = new HashMap<PVar, Class<?>>(var_types);
		exp.getLeft().apply(this);
		Map<PVar,Class<?>> after_left = var_types;
		var_types = before;
		exp.getRight().apply(this);
		for (Iterator<PVar> var_it = var_types.keySet().iterator() ; var_it.hasNext() ;) {
			PVar var = var_it.next();
			if (after_left.containsKey(var)) {
				Class<?> left_type = after_left.get(var);
				Class<?> right_type = var_types.get(var);
				if (left_type != right_type) {
					throw new PeepholeException("Conflicting variable binding across disjunction for variable "+var.getIdentifier().getText()+": "+left_type.getName()+" vs "+right_type.getName());
				}
			} else {
				var_it.remove();
			}
		}
		exp.bound_vars = new HashSet<PVar>(var_types.keySet());

		typeCheck(exp.getLeft(), Boolean.TYPE);
		typeCheck(exp.getRight(), Boolean.TYPE);
		exp.type = Boolean.TYPE;
	}

	public @Override void caseAUnopExp(AUnopExp exp) {
		exp.getUnop().check(exp, this);
	}

	public @Override void outABinopExp(ABinopExp exp) {
		exp.getBinop().check(exp);
	}

	public @Override void outABuiltinExp(ABuiltinExp exp) {
		exp.getBuiltin().check(exp);
	}


	public abstract void PUnop.check(AUnopExp exp, Prepare prepare);

	public void ANegateUnop.check(AUnopExp exp, Prepare prepare) {
		exp.getExp().apply(prepare);

		typeCheck(exp.getExp(), Integer.TYPE);
		exp.type = Integer.TYPE;
	}

	public void AComplementUnop.check(AUnopExp exp, Prepare prepare) {
		Map<PVar,Class<?>> before = new HashMap<PVar,Class<?>>(prepare.var_types);
		exp.getExp().apply(prepare);
		prepare.var_types = before;

		typeCheck(exp.getExp(), Boolean.TYPE);
		exp.type = Boolean.TYPE;
	}


	public abstract void PBinop.check(ABinopExp exp);

	public void APlusBinop.check(ABinopExp exp) {
		typeCheck(exp.getLeft(), Integer.TYPE);
		typeCheck(exp.getRight(), Integer.TYPE);
		exp.type = Integer.TYPE;
	}

	public void AMinusBinop.check(ABinopExp exp) {
		typeCheck(exp.getLeft(), Integer.TYPE);
		typeCheck(exp.getRight(), Integer.TYPE);
		exp.type = Integer.TYPE;
	}

	public void ATimesBinop.check(ABinopExp exp) {
		typeCheck(exp.getLeft(), Integer.TYPE);
		typeCheck(exp.getRight(), Integer.TYPE);
		exp.type = Integer.TYPE;
	}

	public void ADivideBinop.check(ABinopExp exp) {
		typeCheck(exp.getLeft(), Integer.TYPE);
		typeCheck(exp.getRight(), Integer.TYPE);
		exp.type = Integer.TYPE;
	}

	public void AModuloBinop.check(ABinopExp exp) {
		typeCheck(exp.getLeft(), Integer.TYPE);
		typeCheck(exp.getRight(), Integer.TYPE);
		exp.type = Integer.TYPE;
	}

	public void AAndBinop.check(ABinopExp exp) {
		typeCheck(exp.getLeft(), Integer.TYPE);
		typeCheck(exp.getRight(), Integer.TYPE);
		exp.type = Integer.TYPE;
	}

	public void AOrBinop.check(ABinopExp exp) {
		typeCheck(exp.getLeft(), Integer.TYPE);
		typeCheck(exp.getRight(), Integer.TYPE);
		exp.type = Integer.TYPE;
	}

	public void AXorBinop.check(ABinopExp exp) {
		typeCheck(exp.getLeft(), Integer.TYPE);
		typeCheck(exp.getRight(), Integer.TYPE);
		exp.type = Integer.TYPE;
	}

	public void ALtBinop.check(ABinopExp exp) {
		typeCheck(exp.getLeft(), Integer.TYPE);
		typeCheck(exp.getRight(), Integer.TYPE);
		exp.type = Boolean.TYPE;
	}

	public void ALeBinop.check(ABinopExp exp) {
		typeCheck(exp.getLeft(), Integer.TYPE);
		typeCheck(exp.getRight(), Integer.TYPE);
		exp.type = Boolean.TYPE;
	}

	public void AGtBinop.check(ABinopExp exp) {
		typeCheck(exp.getLeft(), Integer.TYPE);
		typeCheck(exp.getRight(), Integer.TYPE);
		exp.type = Boolean.TYPE;
	}

	public void AGeBinop.check(ABinopExp exp) {
		typeCheck(exp.getLeft(), Integer.TYPE);
		typeCheck(exp.getRight(), Integer.TYPE);
		exp.type = Boolean.TYPE;
	}

	public void AEqBinop.check(ABinopExp exp) {
		typeCheck(exp.getRight(), exp.getLeft().type);
		exp.type = Boolean.TYPE;
	}

	public void ANeBinop.check(ABinopExp exp) {
		typeCheck(exp.getRight(), exp.getLeft().type);
		exp.type = Boolean.TYPE;
	}


	public abstract void PBuiltin.check(ABuiltinExp exp);

	public void ACommuteBuiltin.check(ABuiltinExp exp) {
		typeCheck(exp.getExp(), Condition.class);
		exp.type = Condition.class;
	}

	public void ANegateBuiltin.check(ABuiltinExp exp) {
		typeCheck(exp.getExp(), Condition.class);
		exp.type = Condition.class;
	}

	public void ADegreeBuiltin.check(ABuiltinExp exp) {
		typeCheck(exp.getExp(), Label.class);
		exp.type = Integer.TYPE;
	}

	public void ATargetBuiltin.check(ABuiltinExp exp) {
		typeCheck(exp.getExp(), Label.class);
		exp.type = InstructionList.class;
	}

	public void AFormalsBuiltin.check(ABuiltinExp exp) {
		typeCheck(exp.getExp(), MethodSignature.class);
		exp.type = Integer.TYPE;
	}

	public void AReturnsBuiltin.check(ABuiltinExp exp) {
		typeCheck(exp.getExp(), MethodSignature.class);
		exp.type = Integer.TYPE;
	}

}
